﻿using System;
using System.Collections.Generic;
using System.Reflection;
using QuantBox.XApi;
using Skyline;

namespace QuantBox
{
    using SmartQuant;

    internal class Convertor
    {
        private static readonly List<FieldInfo> AccountFields = new();
        private static readonly DepthMarketDataField EmptyMarketData = new();

        private readonly XProvider _provider;
        private readonly Dictionary<string, AccountPosition> _positions = new();
        private readonly IdArray<DepthMarketDataField> _marketData = new();
        private readonly IdArray<bool> _instOpenFlag = new(100);
        private readonly IdArray<bool> _instCloseFlag = new(100);
        private readonly IdArray<bool> _czceInstFlag = new(100);
        private readonly IDictionary<string, Instrument> _instruments = new Dictionary<string, Instrument>();
        private readonly IdArray<TradingTimeRange> _timeRanges = new();
        private IDictionary<string, Instrument>? _instDictCache;

        static Convertor()
        {
            foreach (var field in typeof(AccountField).GetFields())
            {
                AccountFields.Add(field);
            }
            EmptyMarketData.OpenInterest = 0;
            EmptyMarketData.Turnover = 0;
            EmptyMarketData.Volume = 0;
        }

        public Convertor(XProvider provider)
        {
            _provider = provider;
            Init();
        }

        public void Init()
        {
            _instOpenFlag.Clear();
            _instCloseFlag.Clear();
            _czceInstFlag.Clear();
            _instruments.Clear();
        }

        public void Reset()
        {
            for (var i = 0; i < _instCloseFlag.Size; i++)
            {
                _instCloseFlag[i] = true;
                _instOpenFlag[i] = false;
                _marketData[i] = EmptyMarketData;
            }
        }

        private void UpdateInstrumentDict(Instrument inst, bool remove)
        {
            var key = inst.GetSymbol(_provider.GetAltId());
            if (inst.Type == InstrumentType.Stock || inst.Type == InstrumentType.Index)
            {
                key = key + "." + inst.GetExchange(_provider.GetAltId());
            }
            var temp = new Dictionary<string, Instrument>();
            lock (_instruments)
            {
                if (remove)
                {
                    _instruments.Remove(key);
                }
                else
                {
                    _instruments[key] = inst;
                }
                foreach (var item in _instruments)
                {
                    temp.Add(item.Key, item.Value);
                }
            }
            _instDictCache = temp;
        }

        internal void RemoveInstrument(Instrument inst)
        {
            if (_provider.EnableMarketLog)
            {
                _provider.logger.Debug($"{inst.Symbol} remove.");
            }
            UpdateInstrumentDict(inst, true);
        }

        internal void InitInstrument(Instrument inst)
        {
            if (inst.GetExchange(_provider.GetAltId()).ToUpper() == ExchangeNames.CZCE)
            {
                _czceInstFlag[inst.Id] = true;
            }
            if (_provider.EnableMarketLog)
            {
                _provider.logger.Debug($"{inst.Symbol} init.");
            }
            UpdateInstrumentDict(inst, false);
            _marketData[inst.Id] = EmptyMarketData;
            _instCloseFlag[inst.Id] = false;
            _instOpenFlag[inst.Id] = false;
            if (_provider.DiscardOutOfTimeRange)
            {
                _timeRanges[inst.Id] = TradingCalendar.Instance.GetTimeRange(inst, DateTime.Today);
            }
            else
            {
                _timeRanges[inst.Id] = TradingTimeRange.Fulltime;
            }
        }

        public static HedgeFlagType GetHedgeFlag(Order order, HedgeFlagType defaultValue)
        {
            var flag = order.GetHedgeFlag();
            if (flag == HedgeFlagType.Undefined)
            {
                flag = defaultValue;
            }
            return flag;
        }

        public static XApi.OrderSide GetSide(Order order)
        {
            var side = order.GetSide();
            if (side == XApi.OrderSide.Undefined)
            {
                return (XApi.OrderSide)order.Side;
            }
            return side;
        }

        public Instrument GetInstrument(InstrumentField field)
        {
            var instrument = new Instrument((InstrumentType)field.Type, field.Symbol);
            instrument.Description = field.Name();
            instrument.AltId.Add(new AltId(_provider.Id, field.InstrumentID, field.ExchangeID));
            instrument.PutCall = (PutCall)field.OptionsType;
            instrument.Strike = field.StrikePrice;
            instrument.Exchange = field.ExchangeID;
            instrument.CurrencyId = CurrencyId.CNY;
            instrument.TickSize = field.PriceTick;
            instrument.Factor = field.VolumeMultiple;
            instrument.PriceFormat = "F" + QBHelper.GetPrecision(field.PriceTick);
            instrument.Maturity = field.ExpireDate();
            if (!string.IsNullOrEmpty(field.UnderlyingInstrID)
                && field.UnderlyingInstrID != field.ProductID
                && !field.InstrumentID.EndsWith("efp"))
            {
                var underlying = _provider.InstrumentManager.Get(field.UnderlyingInstrID);
                if (underlying == null)
                {
                    //_provider.Logger.Warn($"没有找到合约标的物{field.UnderlyingInstrID},请先导入合约标的物");
                }
                else
                {
#if Compatibility
                    OpenQuant.Helper.AddLeg(instrument, new Leg(underlying));
#else
                    instrument.Legs.Add(new Leg(underlying));
#endif
                }
            }
            return instrument;
        }

        public void ProcessAccount(AccountField account, bool isLast)
        {
            var data = new AccountData(DateTime.Now, AccountDataType.AccountValue, account.AccountID, _provider.Id, _provider.Id);
            foreach (var field in AccountFields)
            {
                var value = field.GetValue(account);
                if (value != null)
                {
                    data.Fields.Add(field.Name, value);
                }
            }
            data.Fields.Add(QBHelper.UserDataName, account);
            data.Fields.Add(QBHelper.UserDataLastName, isLast);            
            _provider.ProcessAccount(data);
        }

        public void ProcessPosition(PositionField? position, bool isLast)
        {
            if (position == null || string.IsNullOrEmpty(position.InstrumentID))
            {
                return;
            }
            if (!_positions.TryGetValue(position.InstrumentID, out var item))
            {
                item = new AccountPosition();
                _positions.Add(position.InstrumentID, item);
            }
            item.AddPosition(position);

            var data = new AccountData(DateTime.Now, AccountDataType.Position, position.AccountID, _provider.Id, _provider.Id);
            data.Fields.Add(AccountDataField.SYMBOL, item.InstrumentId ?? string.Empty);
            data.Fields.Add(AccountDataField.EXCHANGE, item.ExchangeId ?? string.Empty);
            data.Fields.Add(AccountDataField.QTY, item.Qty);
            data.Fields.Add(AccountDataField.LONG_QTY, item.LongQty);
            data.Fields.Add(AccountDataField.SHORT_QTY, item.ShortQty);
            data.Fields.Add(QBHelper.UserDataName, position);
            data.Fields.Add(QBHelper.UserDataLastName, isLast);
            _provider.ProcessAccount(data);
        }

        public void ProcessMarketData(DepthMarketDataField? field)
        {
            if (field == null)
            {
                return;
            }

            if (_provider.EnableMarketLog)
            {
                //_provider.Logger.Debug($"{field.InstrumentID},{field.UpdateTime},{field.Bids[0].Price},{field.Bids[0].Size},{field.Asks[0].Price},{field.Asks[0].Size},{field.LastPrice},{field.Volume}.");
                _provider.logger.Debug($"{field.InstrumentID},{field.UpdateTime},{field.LastPrice},{field.Volume}.");
            }

            _instDictCache!.TryGetValue(field.InstrumentID, out var instrument);
            if (instrument == null)
            {
                if (_provider.EnableMarketLog)
                {
                    _provider.logger.Debug($"unsubscribed tick: {field.InstrumentID}.");
                }
                return;
            }

            field.ExchangeID = instrument.Exchange;
            var instId = instrument.Id;
            var localTime = DateTime.Now;
            var exchangeTime = field.ExchangeDateTime();
            if (exchangeTime.Year == DateTime.MaxValue.Year)
            {
                if (_provider.EnableMarketLog)
                {
                    _provider.logger.Debug($"empty trading time, {field.InstrumentID}");
                }
                exchangeTime = localTime;
            }
            else
            {
                if (_provider.NightTradingTimeCorrection)
                {
                    exchangeTime = QBHelper.CorrectionActionDay(localTime, exchangeTime);
                }
            }

            var time = exchangeTime.TimeOfDay;
            if (_provider.MaxTimeDiffExchangeLocal > 0)
            {
                var diff = Math.Abs((localTime.TimeOfDay - time).TotalMinutes);
                if (diff > _provider.MaxTimeDiffExchangeLocal)
                {
                    if (_provider.EnableMarketLog)
                    {
                        _provider.logger.Debug($"time diff ={diff},{field.InstrumentID}");
                    }
                    return;
                }
            }

            var range = _timeRanges[instId];
            if (!_instOpenFlag[instId])
            {
                if (range.InRange(time))
                {
                    if (_instCloseFlag[instId] && range.IsClose(time))
                    {
                        if (_provider.EnableMarketLog)
                        {
                            _provider.logger.Debug($"already closed, {field.InstrumentID}.");
                        }
                        return;
                    }
                    instrument.SetMarketData(field);
                    _instOpenFlag[instId] = true;
                    _instCloseFlag[instId] = false;
                    if (_provider.EnableMarketLog)
                    {
                        _provider.logger.Debug($"market open, {field.InstrumentID}.");
                    }
                }
                else
                {
                    if (_provider.EnableMarketLog)
                    {
                        _provider.logger.Debug($"market no open, {field.InstrumentID}.");
                    }
                    return;
                }
            }

            var last = _marketData[instId];
            if (range.IsClose(time) && field.ClosePrice > 0)
            {
                instrument.SetMarketData(field);
                _instCloseFlag[instId] = true;
                _instOpenFlag[instId] = false;
                _marketData[instId] = EmptyMarketData;
                if (_provider.EnableMarketLog)
                {
                    _provider.logger.Debug($"market close, {field.InstrumentID}.");
                }
            }
            else
            {
                if (_czceInstFlag[instrument.Id])
                {
                    field.ClosePrice = 0;
                }
                _marketData[instId] = field;
            }

            Ask? ask;
            if (field.Asks?.Length > 0 && field.Asks[0].Size > 0)
            {
#if Compatibility
                ask = OpenQuant.Helper.NewTick<Ask>(localTime, exchangeTime, _provider.Id, instId, field.Asks[0].Price, field.Asks[0].Size);
#else
                ask = new Ask(localTime, exchangeTime, _provider.Id, instId, field.Asks[0].Price, field.Asks[0].Size);
#endif
                //_provider.ProcessMarketData(ask);
            }
            else
            {
                ask = new Ask(localTime, exchangeTime, _provider.Id, instId, 0, 0);
            }

            Bid? bid;
            if (field.Bids?.Length > 0 && field.Bids[0].Size > 0)
            {
#if Compatibility
                bid = OpenQuant.Helper.NewTick<Bid>(localTime, exchangeTime, _provider.Id, instId, field.Bids[0].Price, field.Bids[0].Size);
#else
                bid = new Bid(localTime, exchangeTime, _provider.Id, instId, field.Bids[0].Price, field.Bids[0].Size);
#endif
                //_provider.ProcessMarketData(bid);
            }
            else
            {
                bid = new Bid(localTime, exchangeTime, _provider.Id, instId, 0, 0);
            }

            if (!(field.LastPrice > double.Epsilon))
            {
                if (_provider.EnableMarketLog)
                {
                    _provider.logger.Debug($"empty price, {field.InstrumentID}.");
                }
                return;
            }

            var size = _provider.volumeIsAccumulated ? field.Volume - last.Volume : field.Volume;
            if (_provider.DiscardEmptyTrade
                && _provider.volumeIsAccumulated
                && Math.Abs(size) < double.Epsilon
                && _instOpenFlag[instId])
            {
                if (_provider.EnableMarketLog)
                {
                    _provider.logger.Debug($"empty trade, {field.InstrumentID}.");
                }
                return;
            }
#if Compatibility
            var trade = OpenQuant.Helper.NewTick<Trade>(localTime, exchangeTime, _provider.Id, instId, field.LastPrice, size);
#else
            var trade = new Trade(localTime, exchangeTime, _provider.Id, instId, field.LastPrice, (int)size);
#endif
            double turnover;
            if (_provider.volumeIsAccumulated)
            {
                if (field.Turnover > last.Turnover)
                {
                    turnover = field.Turnover - last.Turnover;
                }
                else
                {
                    turnover = field.Turnover / field.Volume * size;
                }
            }
            else
            {
                turnover = field.Turnover;
            }
            if (!_czceInstFlag[instId])
            {
                turnover /= instrument.Factor;
            }
            trade.SetMarketData(field);
            trade.SetMarketData(turnover, field.OpenInterest);

            if (_provider.RaiseOnTradeFirst)
            {
                _provider.ProcessMarketData(trade);
                _provider.ProcessMarketData(bid);
                _provider.ProcessMarketData(ask);
            }
            else
            {
                _provider.ProcessMarketData(bid);
                _provider.ProcessMarketData(ask);
                _provider.ProcessMarketData(trade);
            }

            if (field.Asks?.Length >= 2 || field.Bids?.Length >= 2)
            {
                var asks = new List<Ask>(field.Asks!.Length);
                if (ask.Size > 0)
                {
                    asks.Add(ask);
                }
                var bids = new List<Bid>(field.Bids!.Length);
                if (bid.Size > 0)
                {
                    bids.Add(bid);
                }

                for (var i = 1; i < field.Asks.Length; i++)
                {
                    var item = field.Asks[i];
                    if (item.Size > 0)
                    {
                        asks.Add(new Ask(localTime, exchangeTime, _provider.Id, instId, item.Price, item.Size));
                    }
                }

                for (var i = 1; i < field.Bids.Length; i++)
                {
                    var item = field.Bids[i];
                    if (item.Size > 0)
                    {
                        bids.Add(new Bid(localTime, exchangeTime, _provider.Id, instId, item.Price, item.Size));
                    }
                }

                if (bids.Count > 1 || asks.Count > 1){
                    var snapshot = new Level2Snapshot(localTime, exchangeTime, _provider.Id, instId, bids.ToArray(), asks.ToArray());
                    _provider.ProcessMarketData(snapshot);
                }
            }
        }

        public static (TickSeries, TickSeries, TickSeries) MarketDataToTick(IEnumerable<DepthMarketDataField> items, Instrument inst)
        {
            var asks = new TickSeries();
            var bids = new TickSeries();
            var trades = new TickSeries();
            var last = EmptyMarketData;
            foreach (var item in items)
            {
                var dateTime = item.ExchangeDateTime();
                var size = item.Volume - last.Volume;
                //var openInterest = item.OpenInterest - last.OpenInterest;
                double turnover;
                if (item.Turnover > last.Turnover)
                {
                    turnover = item.Turnover - last.Turnover;
                }
                else
                {
                    turnover = (item.Turnover / item.Volume) * size;
                }
                var trade = new Trade(dateTime, dateTime, QuantBoxConst.PIdCtp, inst.Id, item.LastPrice, (int)size);
                trade.SetMarketData(turnover, item.OpenInterest);
                trades.Add(trade);
                asks.Add(item.Asks?.Length > 0
                    ? new Ask(dateTime, dateTime, QuantBoxConst.PIdCtp, inst.Id, item.Asks[0].Price, item.Asks[0].Size)
                    : new Ask(dateTime, dateTime, QuantBoxConst.PIdCtp, inst.Id, 0, 0));

                bids.Add(item.Bids?.Length > 0
                    ? new Bid(dateTime, dateTime, QuantBoxConst.PIdCtp, inst.Id, item.Bids[0].Price, item.Bids[0].Size)
                    : new Bid(dateTime, dateTime, QuantBoxConst.PIdCtp, inst.Id, 0, 0));

                last = item;
            }
            return (asks, bids, trades);
        }

        public static XApi.OrderStatus GetStatus(OrderStatus status)
        {
            switch (status)
            {
                case OrderStatus.NotSent:
                    return XApi.OrderStatus.NotSent;
                case OrderStatus.PendingNew:
                    return XApi.OrderStatus.PendingNew;
                case OrderStatus.New:
                    return XApi.OrderStatus.New;
                case OrderStatus.Rejected:
                    return XApi.OrderStatus.Rejected;
                case OrderStatus.PartiallyFilled:
                    return XApi.OrderStatus.PartiallyFilled;
                case OrderStatus.Filled:
                    return XApi.OrderStatus.Filled;
                case OrderStatus.PendingCancel:
                    return XApi.OrderStatus.PendingCancel;
                case OrderStatus.Cancelled:
                    return XApi.OrderStatus.Cancelled;
                case OrderStatus.Expired:
                    return XApi.OrderStatus.Expired;
                case OrderStatus.PendingReplace:
                    return XApi.OrderStatus.PendingReplace;
                case OrderStatus.Replaced:
                    return XApi.OrderStatus.Replaced;
                default:
                    throw new ArgumentOutOfRangeException(nameof(status), status, null);
            }
        }
    }
}
